﻿// ===============================================================================
//  Copyright © ymind.net. All rights reserved .
//  官方网站：http://ymind.net/
//  版权所有：彦铭工作室 老陈出品
// ===============================================================================

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;

namespace Lcsoft.TemplateEngine.XHtmlParser
{
	/// <summary>
	/// 表示 xhtml 文档。
	/// </summary>
	[ComVisible(true)]
	[Serializable]
	public sealed class XHtmlDocument
	{
		private readonly XHtmlNodeCollection _nodes = new XHtmlNodeCollection(null);
		private readonly XHtmlParser _xHtmlParser;
		private XHtmlElement _bodyElement;
		private XHtmlElement _headElement;
		private XHtmlElement _htmlElement;
		private int _nestedCount;

		/// <summary>
		/// 初始化 <see cref="XHtmlDocument"/> 类的新实例。
		/// </summary>
		/// <param name="templateElementPrefix">指定模板语句标签的前缀名。</param>
		/// <param name="initialPath">指定模板文件的初始路径。</param>
		/// <param name="reader">指定包含模板文档的 <see cref="TextReader"/>。</param>
		/// <param name="processSSI">指定是否处理 SSI 嵌套文档。</param>
		/// <param name="maxNestting">指定处理 SSI 嵌套文档时允许的最大嵌套层数。</param>
		/// <param name="templateTags">模板引擎能够处理的语句标签的类型的集合。</param>
		public XHtmlDocument(string templateElementPrefix, string initialPath, TextReader reader, bool processSSI, int maxNestting, SafeDictionary<string, Type> templateTags) : this(templateElementPrefix, initialPath, reader, processSSI, maxNestting, null, templateTags) { }

		/// <summary>
		/// 初始化 <see cref="XHtmlDocument"/> 类的新实例。
		/// </summary>
		/// <param name="templateElementPrefix">指定模板语句标签的前缀名。</param>
		/// <param name="initialPath">指定模板文件的初始路径。</param>
		/// <param name="reader">指定包含模板文档的 <see cref="TextReader"/>。</param>
		/// <param name="processSSI">指定是否处理 SSI 嵌套文档。</param>
		/// <param name="maxNestting">指定处理 SSI 嵌套文档时允许的最大嵌套层数。</param>
		/// <param name="parent">指定父级 <see cref="XHtmlDocument"/>。</param>
		/// <param name="templateTags">模板引擎能够处理的语句标签的类型的集合。</param>
		private XHtmlDocument(string templateElementPrefix, string initialPath, TextReader reader, bool processSSI, int maxNestting, XHtmlDocument parent, SafeDictionary<string, Type> templateTags)
		{
			if (templateElementPrefix == null) throw new ArgumentNullException("templateElementPrefix");
			if (templateElementPrefix.Length == 0) throw new ArgumentOutOfRangeException("templateElementPrefix");

			if (initialPath == null) throw new ArgumentNullException("initialPath");
			if (initialPath.Length == 0) throw new ArgumentOutOfRangeException("initialPath");
			if (Path.IsPathRooted(initialPath) == false) throw new ArgumentException("参数 initialPath 必须是绝对路径。", "initialPath");
			if (Directory.Exists(initialPath) == false) throw new DirectoryNotFoundException(String.Format(@"无法找到指定路径：""{0}""。", initialPath));

			if (reader == null) throw new ArgumentNullException("reader");

			if (maxNestting <= 0) throw new ArgumentOutOfRangeException("maxNestting", "参数 maxNestting 的值必须大于 0。");

			if (templateTags == null) throw new ArgumentNullException("templateTags");
			if (templateTags.Count == 0) throw new ArgumentOutOfRangeException("templateTags");

			this.StatementElementPrefix = String.IsNullOrEmpty(templateElementPrefix) ? "mte" : templateElementPrefix;
			this.InitialPath = initialPath;
			this.MaxNestting = maxNestting;
			this.Parent = parent;
			this.SubDocuments = new Dictionary<XHtmlSSIElement, XHtmlDocument>();
			this.Files = new List<string>();

			if (processSSI) reader = this._ProcessSSIElement(this.InitialPath, new StringBuilder(reader.ReadToEnd()));

			this._xHtmlParser = new XHtmlParser(this.StatementElementPrefix, initialPath, reader, templateTags);

			this._nodes = this._xHtmlParser.GetNodes();
		}

		/// <summary>
		/// 获取 <see cref="XHtmlDocument"/> 包含的子节点的集合。
		/// </summary>
		public XHtmlNodeCollection Nodes
		{
			get { return this._nodes; }
		}

		/// <summary>
		/// 获取处理 SSI 嵌套文档时允许的最大嵌套层数。
		/// </summary>
		public int MaxNestting { get; private set; }

		/// <summary>
		/// 获取模板文件的初始路径。
		/// </summary>
		public string InitialPath { get; private set; }

		/// <summary>
		/// 获取模板语句标签的前缀名。
		/// </summary>
		public string StatementElementPrefix { get; private set; }

		/// <summary>
		/// 获取 <see cref="XHtmlDocument"/> 包含的子文档的文件的集合。
		/// </summary>
		public List<string> Files { get; private set; }

		/// <summary>
		/// 获取 <see cref="XHtmlDocument"/> 中表示 &lt;html&gt; 标签的 <see cref="XHtmlElement"/> 的实例。
		/// </summary>
		public XHtmlElement HtmlElement
		{
			get { return this._htmlElement ?? (this._htmlElement = this.Nodes["html"]); }
		}

		/// <summary>
		/// 获取 <see cref="XHtmlDocument"/> 中表示 &lt;head&gt; 标签的 <see cref="XHtmlElement"/> 的实例。
		/// </summary>
		public XHtmlElement HeadElement
		{
			get { return this.HtmlElement == null ? null : (this._headElement ?? (this._headElement = this.HtmlElement.Nodes["head"])); }
		}

		/// <summary>
		/// 获取 <see cref="XHtmlDocument"/> 中表示 &lt;body&gt; 标签的 <see cref="XHtmlElement"/> 的实例。
		/// </summary>
		public XHtmlElement BodyElement
		{
			get { return this.HtmlElement == null ? null : (this._bodyElement ?? (this._bodyElement = this.HtmlElement.Nodes["body"])); }
		}

		/// <summary>
		/// 获取 <see cref="XHtmlDocument"/> 包含的子级 <see cref="XHtmlDocument"/> 的集合。
		/// </summary>
		internal Dictionary<XHtmlSSIElement, XHtmlDocument> SubDocuments { get; private set; }

		/// <summary>
		/// 获取 <see cref="XHtmlDocument"/> 的父级 <see cref="XHtmlDocument"/>。
		/// </summary>
		internal XHtmlDocument Parent { get; private set; }

		private TextReader _ProcessSSIElement(string initialPath, StringBuilder html)
		{
			if (this._nestedCount > this.MaxNestting) return new StringReader(html.ToString());

			var matches = Regex.Matches(html.ToString(), @"<!--#include file=""([^""]+)""\s*-->", RegexOptions.IgnoreCase | RegexOptions.Singleline);

			foreach (Match match in matches)
			{
				var file = new FileInfo(Path.Combine(initialPath, match.Groups[1].Value.Replace('/', '\\')));

				if (file.Exists == false) continue;
				if (this.Files.Contains(file.FullName) == false) this.Files.Add(file.FullName);

				var includeReader = this._ProcessSSIElement(Path.GetDirectoryName(file.FullName), new StringBuilder(File.ReadAllText(file.FullName)));

				html.Replace(match.Groups[0].Value, includeReader.ReadToEnd());
			}

			this._nestedCount++;

			return new StringReader(html.ToString());
		}

		/// <summary>
		/// 返回包含 <see cref="XHtmlDocument"/> 内容的 .html 格式的字符串。
		/// </summary>
		/// <returns>返回 <see cref="System.String"/>。</returns>
		public string ToHtml()
		{
			var html = new StringBuilder();

			foreach (var node in this.Nodes) html.Append(node.ToHtml());

			return html.ToString();
		}

		/// <summary>
		/// 返回包含模板内容的 .aspx 格式的字符串。
		/// </summary>
		/// <returns>返回 <see cref="System.String"/>。</returns>
		public string ToAspx()
		{
			var aspx = new StringBuilder();

			foreach (var node in this.Nodes)
			{
				var lines = node.ToAspx().Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);

				foreach (var line in lines)
				{
					var lineData = TemplateEngineHelper.ConvertToStringFormat(line);

					if (String.IsNullOrEmpty(lineData)) continue;

					aspx.AppendLine(lineData);
				}
			}

			return aspx.ToString();
		}
	}
}
